﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Net;
using System.Xml.Serialization;

using System.IO;
using Tomahawk.Serialization;
using Ionic.Zlib;
using Tomahawk.Serialization.Reflection;
using System.Xml;
using Tomahawk.Serialization.SerialBoost;
using Tomahawk.Runtime.Threading;

namespace Tomahawk.Runtime.Networking.FileTransfer
{

    #region "Delegates"

    /// <summary>
    /// Delegate to notify file arrivals
    /// </summary>
    public delegate void FileReceivedDelegate(object file, string fileType);

    /// <summary>
    /// Delegate to compare local and remote files
    /// </summary>
    public delegate bool FileComparisonDelegate(FileTransferService.FileDescriptor localFile, FileTransferService.FileDescriptor remoteFile);

    /// <summary>
    /// Delegate to let the client code determine if it wants the file
    /// </summary>
    public delegate bool FileInterestDelegate(FileTransferService.FileDescriptor remoteFile);

    /// <summary>
    /// Delegate to let the client code choose the game to join
    /// </summary>
    public delegate void GameSelectionDelegate(AvailableNetworkSessionCollection availableGames);

    /// <summary>
    /// Delegate to let the client code to load the requested file
    /// </summary>
    public delegate object FileRequestDelegate(FileTransferService.FileDescriptor requestedFile);

    /// <summary>
    /// Delegate to notify finalization of the file transfers
    /// </summary>
    public delegate void FinishDelegate();

    /// <summary>
    /// Delegate to notify the other party's name
    /// </summary>
    /// <param name="otherPartyName"></param>
    public delegate void OtherPartyDelegate(string otherPartyName);

    #endregion

    #region "File Package"

    public class FilePackage
    {

        public class PackagedFile : IXmlSerializable
        {
            [XmlElement("FileData")]
            public object FileData;

            [XmlElement("FileType")]
            public string FileType;

            #region Miembros de IXmlSerializable

            public System.Xml.Schema.XmlSchema GetSchema()
            {
                return null;
            }

            public void ReadXml(System.Xml.XmlReader reader)
            {
                reader.ReadStartElement();
                this.FileType = reader.ReadElementString("FileType");

                string dataType = reader.GetAttribute("type");
                Type tType = AssemblyIndexer.Instance.GetTypeFromAssemblies(dataType);
                XmlSerializer serializer = new XmlSerializer(tType);

                reader.ReadToDescendant(tType.Name);
                this.FileData = serializer.Deserialize(reader);

                reader.ReadEndElement();
                reader.ReadEndElement();
            }

            public void WriteXml(System.Xml.XmlWriter writer)
            {
                //write file type
                writer.WriteElementString("FileType", this.FileType);

                //write object data
                writer.WriteStartElement("FileData");
                writer.WriteAttributeString("type", this.FileData.GetType().FullName);

                //now serialize the element
                Type tType = this.FileData.GetType();
                XmlSerializer valueSerializer = new XmlSerializer(tType);
                valueSerializer.Serialize(writer, this.FileData);

                //close the element
                writer.WriteEndElement();
            }

            #endregion
        }

        [XmlElement("FileList")]
        public SerializableList<PackagedFile> FileList;

        public FilePackage()
        {
            this.FileList = new SerializableList<PackagedFile>();
        }

    }

    #endregion

    /// <summary>
    /// This class implements a network service that uses multiplayer games for
    /// interchanging XmlFiles between two systems
    /// </summary>
    public class FileTransferService : NetworkService
    {

        #region "Serializable Types"

        /// <summary>
        /// Use this class to describe a file
        /// </summary>
        public class FileDescriptor
        {
            /// <summary>
            /// The name of the file this descriptor represents
            /// </summary>
            [XmlAttribute("name")]
            public string FileName;

            /// <summary>
            /// The type of content the filename is
            /// </summary>
            [XmlAttribute("category")]
            public string Category;

            /// <summary>
            /// A value to allow the client code to implement comparision mechanisms
            /// </summary>
            [XmlAttribute("value")]
            public string ComparableValue;
        }

        /// <summary>
        /// Common functionallity for Local and Wanted file lists
        /// </summary>
        public abstract class BaseFileList
        {
            [XmlElement("Files")]
            public SerializableList<FileDescriptor> Files;

            [XmlIgnore()]
            public Dictionary<String, FileDescriptor> FileDictionary;

            /// <summary>
            /// Default Values
            /// </summary>
            public BaseFileList()
            {
                Files = new SerializableList<FileDescriptor>();
                FileDictionary = new Dictionary<string, FileDescriptor>();
            }
        }

        /// <summary>
        /// File Description List
        /// </summary>
        public class FileList : BaseFileList
        {
        }

        /// <summary>
        /// Wanted File List
        /// </summary>
        public class WantedFileList : BaseFileList
        {
        }

        #endregion

        #region "Session configuration"

        /// <summary>
        /// Game types for the type of service
        /// </summary>
        public enum GameTypes
        {
            FileTransfer = DefaultGameTypes.MAX_DEFAULT_GAME_TYPES,
        }

        /// <summary>
        /// Configure the game for this type of service
        /// </summary>
        protected override NetworkSessionProperties getNetworkSessionProperties()
        {
            NetworkSessionProperties retVal = new NetworkSessionProperties();
            retVal[(int)DefaultSessionPropertiesSections.GameType] = (int)GameTypes.FileTransfer;
            return retVal;
        }

        /// <summary>
        /// Configure the player requisites for this type of service
        /// </summary>
        protected override void getConfigGamers(out int maxLocalGamers, out int privateGamerSlots, out int maxGamers)
        {
            maxGamers = 2;
            maxLocalGamers = 1;
            privateGamerSlots = 0;
        }

        #endregion

        #region "Constructor"

        /// <summary>
        /// Constructor
        /// </summary>
        public FileTransferService()
        {
            //data receiving buffers
            packetReader = new PacketReader();
            receivedFileBuffer = new byte[this.receivedFileMaxSize];
            this.utilClearReceivedFileBuffer();
            receivedChunk = new byte[this.chunkSize];
        }

        /// <summary>
        /// Set the whole receive file buffer to zeroes
        /// </summary>
        private void utilClearReceivedFileBuffer()
        {
            for (int i = 0; i < this.receivedFileMaxSize; i++) receivedFileBuffer[i] = 0;
        }

        #endregion

        #region "File Transfer States"

        /// <summary>
        /// Local states of the FileTransfer service
        /// </summary>
        public enum FileTransferStates
        {
            Idle,
            LocalFileListSend,
            LocalFileListSending,
            WaitingForRemoteFileList,
            WaitingForWantedFileList,
            WantedFileListSending,
            WaitingForWantedFiles,
            RequestedFilePreparation,
            RequestedFileSending,
            Finished,
            Finishing,
            Aborted
        }

        /// <summary>
        /// Current state of the file transfer service
        /// </summary>
        private FileTransferStates currentFileTransferState;

        /// <summary>
        /// Return the state of the file transfer
        /// </summary>
        public FileTransferStates CurrentFileTransferState
        { get { return this.currentFileTransferState; } }

        /// <summary>
        /// Change the current local state
        /// </summary>
        protected void changeFileTransferState(FileTransferStates newState)
        {
            //debug
            Engine.Instance.DebugConsole.Warn("FileTranser state changed to: " + newState.ToString());

            //clear the buffer
            this.utilClearReceivedFileBuffer();

            switch (newState)
            {

                case FileTransferStates.Finishing:

                    //if we are the host, end the game now
                    if ((this.currentNetSession.IsHost) && (this.currentNetSession.SessionState == NetworkSessionState.Playing))
                    {
                        try
                        {
                            this.currentNetSession.EndGame();
                        }
                        catch (Exception ex)
                        {
                            this.handleNetworkingException(ex);
                        }
                    }

                    //allow some wait time to keep calling Session.Update() for a while
                    this.finishingTimeLeft = this.finishingTime;

                    break;

                case FileTransferStates.Finished:

                    //finish the session
                    this.disposeSession();

                    break;

                case FileTransferStates.Aborted:

                    //dispose the session
                    this.disposeSession();

                    break;
            }

            //set the state
            this.currentFileTransferState = newState;
        }

        #endregion

        #region "File List Management"

        /// <summary>
        /// Default file list used for local files
        /// </summary>
        private FileList localFileList;

        /// <summary>
        /// Remote file list (created when data arrives)
        /// </summary>
        private FileList remoteFileList;

        /// <summary>
        /// Empty the file list
        /// </summary>
        public void FileListReset()
        {
            if (this.localFileList != null)
            {
                this.localFileList.FileDictionary.Clear();
                this.localFileList.Files.Clear();
            }
            this.localFileList = new FileList();
        }

        /// <summary>
        /// Add a file description to the file list
        /// </summary>
        public void FileListAdd(string fileName, string category, string value)
        {
            FileDescriptor desc = new FileDescriptor();
            desc.FileName = fileName;
            desc.Category = category;
            desc.ComparableValue = value;
            this.localFileList.Files.Add(desc);
            this.localFileList.FileDictionary.Add(desc.Category + "@" + desc.FileName, desc);
        }

        /// <summary>
        /// Get a file descriptor from the local file list
        /// </summary>
        public FileDescriptor GetLocalFile(string Category, string Id)
        {
            FileDescriptor retVal = null;
            this.localFileList.FileDictionary.TryGetValue(Category + "@" + Id, out retVal);
            return retVal;
        }

        #endregion

        #region "Update"

        #region "Send"

        private float sendInterval = 0.10f;

        private float sendIntervalElapsed = 0;

        private float finishingTimeLeft = 0.0f;

        private float finishingTime = 1.5f;

        /// <summary>
        /// Update the data to be sent
        /// </summary>
        public override void OnUpdateSend(float elapsedTime)
        {
            base.OnUpdateSend(elapsedTime);

            switch (this.currentFileTransferState)
            {
                case FileTransferStates.Finishing:

                    //delayed finalization
                    this.finishingTimeLeft -= elapsedTime;
                    if (this.finishingTimeLeft <= 0)
                    {
                        //change state to finished
                        this.changeFileTransferState(FileTransferStates.Finished);

                        //notify client code
                        if (this.OnTransferFinished != null) this.OnTransferFinished();
                    }

                    break;

                case FileTransferStates.LocalFileListSend:

                    //prepare the file to be sent
                    this.prepareSendFile(this.localFileList);

                    //state to wait for ack for file list received
                    this.changeFileTransferState(FileTransferStates.LocalFileListSending);

                    break;

                case FileTransferStates.LocalFileListSending:
                case FileTransferStates.RequestedFileSending:
                case FileTransferStates.WantedFileListSending:

                    //wait a certain amount of time before sending more packets
                    this.sendIntervalElapsed += elapsedTime;
                    if (this.sendIntervalElapsed > this.sendInterval)
                    {
                        this.sendIntervalElapsed = 0;

                        //send next file chunk
                        try
                        {
                            this.sendFileChunk();
                        }
                        catch (Exception ex)
                        {
                            this.handleNetworkingException(ex);
                        }
                    }

                    break;

                case FileTransferStates.RequestedFilePreparation:

                    //are we done preparing the requested files?
                    if (this.preparedPackageDone)
                    {
                        //prepare the file for sending
                        this.prepareSendFile(preparedPackage);

                        //change state to sending
                        this.changeFileTransferState(FileTransferStates.RequestedFileSending);
                    }

                    break;

            }

        }

        private PacketWriter packectWriter;
        private byte[] fileChunk;
        private int chunkSize = 32;

        private byte[] compressedObjectBuffer;
        private int compressedObjectBufferPosition;

        private bool firstChunk = false;

        /// <summary>
        /// Utility to Copy Stream
        /// </summary>
        private void copyStream(System.IO.Stream src, System.IO.Stream dest)
        {
            byte[] buffer = new byte[1024];
            int len = src.Read(buffer, 0, buffer.Length);
            while (len > 0)
            {
                dest.Write(buffer, 0, len);
                len = src.Read(buffer, 0, buffer.Length);
            }
            dest.Flush();
        }


        /// <summary>
        /// Send the local file list to the host
        /// </summary>
        private void prepareSendFile(object fileObject)
        {
            //serialize the object
            //byte[] serializedObjectBuffer = SerializationPool.Instance.SaveObject(fileObject);
            MemoryStream ms = SerialBoost.Instance.Serialize(fileObject);
            byte[] serializedObjectBuffer = ms.GetBuffer();
            ms.Close();

            //compress the object
            System.IO.MemoryStream serializedObjectStream = new MemoryStream(serializedObjectBuffer);
            System.IO.MemoryStream msSinkCompressed = new System.IO.MemoryStream();
            ZlibStream zOut = new ZlibStream(msSinkCompressed, CompressionMode.Compress, CompressionLevel.LEVEL9_BEST_COMPRESSION);
            this.copyStream(serializedObjectStream, zOut);

            //free resources
            zOut.Close();
            serializedObjectStream.Dispose();
            serializedObjectStream = null;
            serializedObjectBuffer = null;

            //leave the compressed object in the buffer
            compressedObjectBuffer = msSinkCompressed.GetBuffer();
            compressedObjectBufferPosition = 0;

            //create the chunk array
            fileChunk = new byte[this.chunkSize];

            //crate the packet writer
            this.packectWriter = new PacketWriter();

            //first chunk flag on
            firstChunk = true;

            //debug
            Engine.Instance.DebugConsole.Warn("File prepared to be sent to: " + this.currentNetSession.Host.Gamertag);
        }

        /// <summary>
        /// Send a chunk of the file
        /// </summary>
        private void sendFileChunk()
        {
            //last chunk flag
            bool lastChunk = false;

            //copy a portion of the file to the array
            for (int i = compressedObjectBufferPosition; i < compressedObjectBufferPosition + chunkSize; i++)
            {
                if (i < compressedObjectBuffer.Length)
                {
                    this.fileChunk[i - compressedObjectBufferPosition] = this.compressedObjectBuffer[i];
                }
                else
                {
                    this.fileChunk[i - compressedObjectBufferPosition] = 0;
                    lastChunk = true;
                }
            }

            //advance position
            compressedObjectBufferPosition += this.chunkSize;

            //pack the data
            this.packectWriter.Write(this.firstChunk);
            if (this.firstChunk) this.firstChunk = false;
            this.packectWriter.Write(lastChunk);
            this.packectWriter.Write(this.fileChunk);

            //send data
            //NOTE: error handling for this piece of code is done in the calling methods
            if (this.currentNetSession.IsHost)
            {
                LocalNetworkGamer gamer = this.currentNetSession.LocalGamers[0];
                gamer.SendData(this.packectWriter, SendDataOptions.ReliableInOrder, this.currentNetSession.RemoteGamers[0]);
            }
            else
            {
                LocalNetworkGamer gamer = this.currentNetSession.LocalGamers[0];
                gamer.SendData(this.packectWriter, SendDataOptions.ReliableInOrder, this.currentNetSession.Host);
            }

            //debug info
            //Engine.Instance.DebugConsole.Warn("File Transfer Service: file chunk sent!");

            //
            if (lastChunk)
            {
                switch (this.currentFileTransferState)
                {

                    //
                    // Finished sending the local file list
                    //

                    case FileTransferStates.LocalFileListSending:
                        this.changeFileTransferState(FileTransferStates.WaitingForWantedFileList);
                        break;

                    //
                    // Finished sending the wanted file list
                    //

                    case FileTransferStates.WantedFileListSending:

                        if (this.localWantedFiles.Files.Count > 0)
                        {
                            //if we wanted any file, wait for them to be sent
                            this.changeFileTransferState(FileTransferStates.WaitingForWantedFiles);
                        }
                        else
                        {
                            //if not, go to next state
                            if (this.currentNetSession.IsHost)
                            {
                                //the server will receive now the available file list from client
                                this.changeFileTransferState(FileTransferStates.LocalFileListSend);
                            }
                            else
                            {
                                //the client wants no files from server, then finish
                                this.changeFileTransferState(FileTransferStates.Finishing);
                            }
                        }
                        break;

                    //
                    // Finished sending one of the requested files
                    // 

                    case FileTransferStates.RequestedFileSending:

                        /*this.sendNextWantedFile();*/

                        if (this.currentNetSession.IsHost)
                        {
                            //the server will stop the game now, as there nothing more to do
                            this.changeFileTransferState(FileTransferStates.Finishing);
                        }
                        else
                        {
                            //the clien will receive now the FileList from the server
                            this.changeFileTransferState(FileTransferStates.WaitingForRemoteFileList);
                        }

                        break;

                }
            }
        }

        #endregion

        #region "Receive"

        /// <summary>
        /// Update the data receival
        /// </summary>
        public override void OnUpdateReceive(float elapsedTime)
        {
            base.OnUpdateReceive(elapsedTime);

            switch (this.currentFileTransferState)
            {
                case FileTransferStates.WaitingForRemoteFileList:
                case FileTransferStates.WaitingForWantedFileList:
                case FileTransferStates.WaitingForWantedFiles:

                    //receive a file chunk
                    try
                    {
                        this.receiveFileChunk();
                    }
                    catch (Exception ex)
                    {
                        this.handleNetworkingException(ex);
                    }

                    break;
            }

        }

        PacketReader packetReader;
        byte[] receivedFileBuffer;
        byte[] receivedChunk;
        int receivedFileMaxSize = 1024 * 16;
        int receivedFilePos;
        int totalChunks;

        /// <summary>
        /// Check for files sent in by the server
        /// </summary>
        private void receiveFileChunk()
        {
            //NOTE: Error handling for this piece of code is done in the calling method
            foreach (LocalNetworkGamer gamer in this.currentNetSession.LocalGamers)
            {
                if (gamer.IsDataAvailable)
                {
                    //debug
                    //Engine.Instance.DebugConsole.Warn("Data available for player: " + gamer.Gamertag);

                    //get the data in a packetReader
                    NetworkGamer sender = null;
                    gamer.ReceiveData(this.packetReader, out sender);

                    //if not coming for ourself
                    if (!sender.IsLocal)
                    {
                        bool firstChunk = this.packetReader.ReadBoolean();
                        bool lastChunk = this.packetReader.ReadBoolean();
                        this.packetReader.Read(this.receivedChunk, 0, this.chunkSize);

                        //restart counters
                        if (firstChunk)
                        {
                            receivedFilePos = 0;
                            totalChunks = 0;
                        }

                        //copy the chunk received to the array
                        for (int i = 0; i < this.chunkSize; i++)
                        {
                            this.receivedFileBuffer[i + receivedFilePos] = this.receivedChunk[i];
                        }

                        //count the chunk
                        this.totalChunks++;
                        this.receivedFilePos += this.chunkSize;

                        //if it's the last chunk, process the received file
                        if (lastChunk)
                        {
                            this.processReceivedData(this.receivedFileBuffer, this.totalChunks * this.chunkSize);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Process received data
        /// </summary>
        private void processReceivedData(byte[] data, int size)
        {
            //guess the right size of the data received
            int realSize = size;
            while (data[realSize] == 0) { realSize--; }
            realSize++;

            //uncompress data
            System.IO.MemoryStream msSinkCompressed = new MemoryStream(data, 0, realSize);
            System.IO.MemoryStream msSinkDecompressed = new System.IO.MemoryStream();
            ZlibStream zOut = new ZlibStream(msSinkDecompressed, CompressionMode.Decompress);
            this.copyStream(msSinkCompressed, zOut);

            //convert data into a string
            string xmlString = Encoding.UTF8.GetString(msSinkDecompressed.GetBuffer(), 0, (int)msSinkDecompressed.Length);

            //free resources
            zOut.Close();
            zOut = null;
            msSinkCompressed.Dispose();
            msSinkCompressed = null;

            //clear the received buffer
            this.utilClearReceivedFileBuffer();

            //get the type name
            int firstClose = xmlString.IndexOf('>');
            int beginElement = xmlString.IndexOf('<', firstClose);
            int endElement = xmlString.IndexOf(' ', beginElement);
            string sType = xmlString.Substring(beginElement + 1, (endElement - beginElement) - 1);

            //get the real length
            string closingTokenStr = String.Format("</{0}>", sType);
            int candidateClosing = beginElement; int closingPos = 0;
            while (candidateClosing != -1)  //searchs the last occurende of closingToken
            {
                candidateClosing = xmlString.IndexOf(closingTokenStr, candidateClosing + 1);
                if (candidateClosing >= closingPos) closingPos = candidateClosing;
            }
            int realLength = closingPos + closingTokenStr.Length;
            string trimed = xmlString.Substring(0, realLength);

            //creates a memory stream from the trimed string
            byte[] xmlData = Encoding.UTF8.GetBytes(trimed);
            MemoryStream ms = new MemoryStream(xmlData, false);

            //is the file a file list??
            if (sType == "FileList")
            {
                this.remoteFileList = (FileList)SerialBoost.Instance.Deserialize(typeof(FileList), ms);
                ms.Close();

                //debug info
                Engine.Instance.DebugConsole.Warn(
                    String.Format("File List received, containing {0} file descriptions.", this.remoteFileList.Files.Count));

                //compute the local list of wanted files, comparing the ones offered by the other party
                this.processLocalWantedFiles();
            }
            //is the file a wanted file list?
            else if (sType == "WantedFileList")
            {
                WantedFileList remoteWantedFileList = (WantedFileList)SerialBoost.Instance.Deserialize(typeof(WantedFileList), ms);
                ms.Close();

                //debug info
                Engine.Instance.DebugConsole.Warn(
                    String.Format("Received a list of {0} wanted files.", remoteWantedFileList.Files.Count));

                //start processing the requested files
                this.processRemoteWantedFileList(remoteWantedFileList);
            }
            else if (sType == "FilePackage")
            {
                //create the file package object
                FilePackage package = (FilePackage)SerialBoost.Instance.Deserialize(typeof(FilePackage), ms);
                ms.Close();

                //raise client event for each file
                foreach (FilePackage.PackagedFile file in package.FileList)
                {
                    if (this.OnFileReceived != null) this.OnFileReceived(file.FileData, file.FileType);
                }

                //next state
                if (this.currentNetSession.IsHost)
                {
                    //the server will automatically send the local files to the client
                    //when the last requested filed is received
                    this.changeFileTransferState(FileTransferStates.LocalFileListSend);
                }
                else
                {
                    //the client will cease the connection when all the requested files
                    //are received
                    this.changeFileTransferState(FileTransferStates.Finishing);
                }
            }
            else
            {
                throw new Exception("Unrecognized file type received!");
            }

            //free the buffer
            msSinkDecompressed.Dispose();
            msSinkDecompressed = null;
        }

        #endregion

        #endregion

        #region "Lobby management"

        /// <summary>
        /// Update the lobby state
        /// </summary>
        protected override void OnUpdateLobby(float elapsedTime)
        {
            base.OnUpdateLobby(elapsedTime);

            //inmediatelly ready
            try
            {
                foreach (LocalNetworkGamer gamer in this.currentNetSession.LocalGamers)
                {
                    if (currentNetSession.SessionState == NetworkSessionState.Lobby)
                        gamer.IsReady = true;
                }
            }
            catch (Exception ex)
            {
                this.handleNetworkingException(ex);
            }

            switch (this.currentFileTransferState)
            {

                case FileTransferStates.Finishing:

                    //delayed finalization
                    this.finishingTimeLeft -= elapsedTime;
                    if (this.finishingTimeLeft <= 0)
                    {
                        //change state to finished
                        this.changeFileTransferState(FileTransferStates.Finished);

                        //notify client code
                        if (this.OnTransferFinished != null) this.OnTransferFinished();
                    }

                    break;
            }
        }

        #endregion

        #region "Server / Client working modes"

        /// <summary>
        /// Available working modes
        /// </summary>
        public enum WorkModes
        {
            Server,
            Client
        }

        /// <summary>
        /// Gets or Sets the working mode
        /// </summary>
        public WorkModes WorkMode { get; set; }

        /// <summary>
        /// Sets the initial state depending on our working mode
        /// </summary>
        protected override void OnGameStarted()
        {
            base.OnGameStarted();

            switch (this.WorkMode)
            {
                case WorkModes.Client:
                    this.changeFileTransferState(FileTransferStates.LocalFileListSend);
                    break;

                case WorkModes.Server:
                    this.changeFileTransferState(FileTransferStates.WaitingForRemoteFileList);
                    break;
            }

            //raise the event for other parties
            foreach (NetworkGamer g in this.currentNetSession.RemoteGamers)
            {
                if (this.OnOtherPartyConnection != null) this.OnOtherPartyConnection(g.Gamertag);
            }
        }

        #endregion

        #region "Events"

        /// <summary>
        /// This event is raised when other party is added to the game
        /// </summary>
        public event OtherPartyDelegate OnOtherPartyConnection;

        /// <summary>
        /// Handle this event to process a new received file
        /// </summary>
        public event FileReceivedDelegate OnFileReceived;

        /// <summary>
        /// Handle this event to provide you own file comparison rules.
        /// You must return true if you want the remote file.
        /// </summary>
        public event FileComparisonDelegate OnFileOfferedCompare;

        /// <summary>
        /// Handle this event to decide if you want a file offered by the remote part
        /// </summary>
        public event FileInterestDelegate OnFileOffered;

        /// <summary>
        /// Handle this event to select the appropiate game to join
        /// </summary>
        public event GameSelectionDelegate OnSelectGameToJoin;

        /// <summary>
        /// Handle this event to load the requested object 
        /// </summary>
        public event FileRequestDelegate OnFileRequested;

        /// <summary>
        /// Handle this event to provide transfer finish code
        /// </summary>
        public event FinishDelegate OnTransferFinished;

        #endregion

        #region "NetService Event Handling"

        /// <summary>
        /// The GAME has ended, react accordingly
        /// </summary>
        protected override void OnGameEnded(GameEndedEventArgs e)
        {
            base.OnGameEnded(e);

            //change state to Finished 
            //(will cause the session being disposed)
            this.changeFileTransferState(FileTransferStates.Finished);

            //notify client code
            if (this.OnTransferFinished != null) this.OnTransferFinished();
        }

        /// <summary>
        /// The SESSION has ended
        /// </summary>
        protected override void OnSessionEnded(NetworkSessionEndedEventArgs e)
        {
            base.OnSessionEnded(e);

            //if we get here before setting the state to idle, it's mean that
            //something may had gone wrong with the session 
            if (this.currentFileTransferState != FileTransferStates.Finished)
            {
                //change state
                this.changeFileTransferState(FileTransferStates.Aborted);

                //notify client code
                if (this.OnTransferFinished != null) this.OnTransferFinished();
            }
        }

        /// <summary>
        /// When server is bored because no players where found
        /// </summary>
        public override void OnServerTimeout()
        {
            base.OnServerTimeout();

            //finish current game/session
            this.changeFileTransferState(FileTransferStates.Finishing);
        }

        /// <summary>
        /// Handle exceptions on the networking code
        /// </summary>
        protected override void handleNetworkingException(Exception ex)
        {
            //call base code (will dispose the session)
            base.handleNetworkingException(ex);

            //set the abort state
            this.changeFileTransferState(FileTransferStates.Aborted);

            //notify client code
            if (this.OnTransferFinished != null) this.OnTransferFinished();
        }

        /// <summary>
        /// Handle the premature departure of a player
        /// </summary>
        protected override void OnGamerLeft(GamerLeftEventArgs e)
        {
            base.OnGamerLeft(e);

            //if the game is not being finished, then it's a premature departure
            if ((this.currentFileTransferState != FileTransferStates.Finished)
                && (this.currentFileTransferState != FileTransferStates.Finishing)
                    && (this.currentFileTransferState != FileTransferStates.Idle))
            {
                //the player left before the transaction was finished
                this.changeFileTransferState(FileTransferStates.Aborted);

                //notify client code
                if (this.OnTransferFinished != null) this.OnTransferFinished();
            }
        }

        #endregion

        #region "Joning Games"

        /// <summary>
        /// When games are found, we should let the client code decide which game to join
        /// </summary>
        protected override void OnGamesFound()
        {
            //don't call the base implementation!
            //base.OnGamesFound();

            //let the client code decide which game to join
            if (this.OnSelectGameToJoin != null) this.OnSelectGameToJoin(this.foundNetSessions);
        }

        #endregion

        #region "Wanted Files"

        WantedFileList localWantedFiles;

        WantedFileList remoteWantedFiles;

        /// <summary>
        /// Compare local and remote file list, and let the user code determine if 
        /// the file is wanted or not
        /// </summary>
        private void processLocalWantedFiles()
        {
            //new wanted file list
            this.localWantedFiles = new WantedFileList();

            //for each file offered by the other party
            foreach (FileDescriptor remoteFile in this.remoteFileList.Files)
            {
                //by default we want the file
                bool wantThisFile = true;

                //do we have this file?
                bool foundThisFile = false;

                //if already got the file
                FileDescriptor localFile = this.GetLocalFile(remoteFile.Category, remoteFile.FileName);
                if (localFile != null)
                {
                    //we have this file already, but maybe not the same version/value
                    foundThisFile = true;

                    //if exact same version
                    if (localFile.ComparableValue == remoteFile.ComparableValue)
                    {
                        wantThisFile = false;
                    }
                    else
                    {
                        //if not the same version, let the client code compare the file
                        if (this.OnFileOfferedCompare == null) throw new Exception("OnFileWanted event must be handled by client code!");

                        //let the client code decide if we want the file
                        wantThisFile = this.OnFileOfferedCompare(localFile, remoteFile);
                    }
                }


                //we don't have the file yet, let the client code decide if we want it
                if (!foundThisFile)
                {
                    wantThisFile = this.OnFileOffered(remoteFile);
                }

                //Add the remote file to the wanted list, if wanted
                if (wantThisFile)
                {
                    this.localWantedFiles.Files.Add(remoteFile);
                }
            }

            //debug info
            Engine.Instance.DebugConsole.Warn(
                String.Format("File Transfer Service: Remote File List processed, we want {0} of their files.",
                this.localWantedFiles.Files.Count));

            //prepare the wanted file list to be sent
            this.prepareSendFile(this.localWantedFiles);

            //set the sstate to "sending the wanted file list"
            this.changeFileTransferState(FileTransferStates.WantedFileListSending);

        }

        /// <summary>
        /// Prepare the list of files that the other party had requested
        /// </summary>
        private void processRemoteWantedFileList(WantedFileList remoteWatedFileList)
        {
            //save the list to be used while sending the files
            this.remoteWantedFiles = remoteWatedFileList;

            //no files wanted
            if (remoteWatedFileList.Files.Count == 0)
            {
                Engine.Instance.DebugConsole.Warn("The other party want none of our files");
            }
            else
            {

                /*
                //load all wanted files into a FilePackage
                FilePackage package = new FilePackage();

                //ask client code for the file
                foreach (FileDescriptor file in remoteWantedFiles.Files)
                {
                    //request the client code for the data
                    object fileObject = null;
                    if (this.OnFileRequested != null) fileObject = this.OnFileRequested(file);

                    //add to package
                    if (fileObject != null)
                    {
                        //add data to a PackagedFile structure
                        FilePackage.PackagedFile packedFile = new FilePackage.PackagedFile();
                        packedFile.FileData = fileObject;
                        packedFile.FileType = file.Category;

                        //add to package
                        package.FileList.Add(packedFile);
                    }
                }

                //
                //at this point all files are packaged into package object
                //

                //prepare the file for sending
                this.prepareSendFile(package);

                //set the state
                this.changeFileTransferState(FileTransferStates.RequestedFileSending);*/

                //begin preparing the package in other thread
                this.beginPackagePreapartion();

                this.changeFileTransferState(FileTransferStates.RequestedFilePreparation);
            }

        }

        #region "Threaded Package Preparation"

        private FilePackage preparedPackage = null;
        private bool preparedPackageDone = false;
        private ManagedThread packageThread;
        private ThreadTask packageThreadTask;

        /// <summary>
        /// Starts the package creation thread
        /// </summary>
        private void beginPackagePreapartion()
        {
            //set the peparation ready
            this.preparedPackageDone = false;
            this.preparedPackage = new FilePackage();

            //create a threaded task
            packageThread = new Tomahawk.Runtime.Threading.ManagedThread(4);
            packageThreadTask = new Tomahawk.Runtime.Threading.ThreadTask(doPackagePreparation);
            packageThread.AddTask(packageThreadTask);
        }

        /// <summary>
        /// Threaded package creation
        /// </summary>
        private void doPackagePreparation(object input)
        {
            //ask client code for the file
            foreach (FileDescriptor file in remoteWantedFiles.Files)
            {
                //request the client code for the data
                object fileObject = null;
                if (this.OnFileRequested != null) fileObject = this.OnFileRequested(file);

                //add to package
                if (fileObject != null)
                {
                    //add data to a PackagedFile structure
                    FilePackage.PackagedFile packedFile = new FilePackage.PackagedFile();
                    packedFile.FileData = fileObject;
                    packedFile.FileType = file.Category;

                    //add to package
                    preparedPackage.FileList.Add(packedFile);
                }
            }

            //flag the process as done
            this.preparedPackageDone = true;
        }

        #endregion

        /// <summary>
        /// If no servers were found, notify a transfer complete
        /// </summary>
        protected override void OnNoGamesFound()
        {
            //don't call the base implementation!
            //base.OnNoGamesFound();
            if (this.OnTransferFinished != null) this.OnTransferFinished();
        }

        #endregion

    }

}
